iNaturalist is a global, online community of citizen scientists who are mapping the biodiversity of our world. The best part is: the data is available for free and it is all geolocated!
Check out the home page of iNaturalist for a brief introduction to this citizen science project.
Watch the videos below to get an idea of how iNaturalist works, and how you can use it.
Observe Nature with iNaturalist
embed_url("https://www.youtube.com/watch?v=Mb_i-WoUKt0")
Identify Nature with iNaturalist
embed_url("https://www.youtube.com/watch?v=ap1aLIVbxh8")
Seven reasons to contribute to iNaturalist as an identifier
embed_url("https://www.youtube.com/watch?v=YM8D63h35LM")
Watch the videos below for examples of how iNaturalist data has been used for sustainability and resilience. Start thinking about how you could use it for your final project.
iNaturalist in ecological research & applications for the 2020 ESA annual meeting
embed_url("https://www.youtube.com/watch?v=F1qedUYwNvY&list=PLduYc6-ie4l11RvwDvDYqF4iX0lVCckfx&index=2")
How iNat Data Were Used to Study Biodiversity of Redlined Districts in California
embed_url("https://www.youtube.com/watch?v=gxpx7kwCVTQ")
iNaturalist is a wonderful tool that helps bridge the gap between citizen scientists and research scientists. When iNaturalist observers take high quality photographs of taxa, upload them with geodata, and include relevant notes, these data can then be used in a multitude of studies such examining biodiversity, monitoring endangered species, understanding species hybridization, tracking invasive species, etc.
First, load the necessary libraries:
library(rinat)
library(sf)
## Linking to GEOS 3.11.0, GDAL 3.5.3, PROJ 9.1.0; sf_use_s2() is TRUE
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.3 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.0
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ lubridate::hms() masks vembedr::hms()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tmap)
## Breaking News: tmap 3.x is retiring. Please test v4, e.g. with
## remotes::install_github('r-tmap/tmap')
library(leaflet)
library(osmdata)
## Data (c) OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright
library(plotly)
##
## Attaching package: 'plotly'
##
## The following object is masked from 'package:ggplot2':
##
## last_plot
##
## The following object is masked from 'package:stats':
##
## filter
##
## The following object is masked from 'package:graphics':
##
## layout
Next, let’s choose a single genus (group of related species) to focus on for now. Let’s find out about the Chaminade University mascot: the Silversword, or ʻĀhinahina in the Hawaiian language.
First, go to https://www.inaturalist.org/. Click on the tab at the top labeled “Explore”. Then, in the search box labeled “Species”, type in “Silverswords” and click on the “Silverswords, Genus Argyroxiphium” selection that pops up. Then in the “Location” search box, type in “Maui County” and choose the selection that pops up: “Maui County, HI, USA”. That should take you to this page: https://www.inaturalist.org/observations?place_id=1607&taxon_id=68284
This page tells us how many observations have been made of the genus (1,237 when I checked), how many species of the genus are present, and how many users have made identifications and observations. You can also see all of the observations and the beautiful pictures!
The other important thing to note, is that the URL for this page
includes some key information for our data retrieval purposes: the
“place_id” and “taxon id”. You can see at the end of the URL that the
place id for Maui is “1607” and the taxon id for Silverswords is 68284
(place_id=1607&taxon_id=68284).
Now that we have a place id and a taxon id, we can retrieve data for Silverswords on the island of Maui.
But before we do that, let’s talk about data retrieval etiquette.
Data retrieval (or data downloading) using an API is an amazing feature of modern data science, but we shouldn’t take it for granted. The API will “throttle”, or slow down, your downloading, or even block your IP address if you download too much, too quickly.
The non-profit that supports iNaturalist doesn’t charge for downloading data, because they believe in open data and supporting citizen science initiatives. They also don’t require registration for simply downloading observations. That being said, downloading data consumes resources that can affect other users, and it’s bad form (and bad programming) to download data unnecessarily.
There are a few best practices for downloading data in ways that won’t cause problems for other users:
-Use the server-side filters the API provides to only return results for your area of interest and your taxon(s) of interest. See below for examples of sending spatial areas and a taxon name when calling the API from R.
-Always save your results to disk, and don’t download data more than once. Your code should check to see if you’ve already downloaded something before getting it again.
-Don’t download more than you need. The API will throttle you (reduce your download speed) if you exceed more than 100 calls per minute. Spread it out.
-The API is not designed for bulk downloads. For that, look at the Export tool (requires a user account and sign in).
Now we are ready to retrieve iNaturalist observations. The
rinat package makes this easy with the
get_inat_obs(). If you skipped the previous section on data
retrieval etiquette, go back and read it before you proceed!
# Note we have to rearrange the coordinates of the bounding box a little bit
# to give get_inat_obs() what it expects
inat_obs_df <- get_inat_obs(taxon_id = 68284, #taxon id from URL
place_id = 1607, #place id from URL
quality = "research", #specifies research grade only
geo = TRUE, #Specifies that we want geocoordinates
maxresults = 100) #Limits results
save(inat_obs_df, file = "maui_silverswords_inat.Rdata")
inat_obs_sf <- inat_obs_df %>%
select(longitude, latitude, datetime, common_name, scientific_name, image_url, user_login) %>%
st_as_sf(coords=c("longitude", "latitude"), crs=4326)
dim(inat_obs_sf)
## [1] 100 6
Let’s check to see that our inat_obs_sf object was
created and that we can map the data. Let’s add in some symbology to
check that we have all the species in the genus, by assigning
color to common_name.
ggplot() +
geom_point(data = inat_obs_df, aes(x = longitude, y = latitude, color = common_name, text = common_name))
## Warning in geom_point(data = inat_obs_df, aes(x = longitude, y = latitude, :
## Ignoring unknown aesthetics: text
Okay, so our data is showing up (at the appropriate lat/long), and we have a legend that shows different colors for different species. But now we need to turn it into a map that people can read, by adding contextual information. Let’s use what we have learned in previous lessons to add contextual data from OpenStreetMap.
First let’s create our bounding box for the island of Maui.
Next, let’s choose our location and create a bounding box. Let’s
explore Maui. We can use the osmdata package function
getbb() to get the bounding box.
maui_bb <- getbb("Maui")
maui_bb
## min max
## x -156.69726 -155.97909
## y 20.57443 21.03156
In the code below, we are creating data objects that we will use as layers on our map. Let’s create layers of large streets, small streets and paths, the coastline, the national park, and other protected areas.
# retrieving data of streets on Maui
maui_streets <- maui_bb %>%
opq() %>%
add_osm_feature("highway", c("motorway", "primary", "secondary", "tertiary")) %>%
osmdata_sf()
# retrieving data of small streets on Maui
maui_small_streets <- maui_bb %>%
opq() %>%
add_osm_feature(key = "highway", value = c("residential", "living_street", "unclassified", "service", "footway")) %>%
osmdata_sf()
# retrieving data of coastline on Maui
maui_coast <- maui_bb %>%
opq() %>%
add_osm_feature(key = "natural", value = "coastline") %>%
osmdata_sf()
# retrieving data of national park on Maui
maui_np <- maui_bb %>%
opq() %>%
add_osm_feature(key = "boundary", value = "national_park") %>%
osmdata_sf()
# retrieving data of protected areas on Maui
maui_protected <- maui_bb %>%
opq() %>%
add_osm_feature(key = "boundary", value = "protected_area") %>%
osmdata_sf()
Now let’s create a map with all of our data. We are going to assign
this map to the object p, because we are going to use it
for a couple of different maps.
# visualising all retrieved features over each other to form a map of Maui
p <- ggplot() +
geom_sf(data = maui_streets$osm_lines, inherit.aes = FALSE, color = "#ffbe7f", size = .4, alpha = .8) +
geom_sf(data = maui_small_streets$osm_lines, inherit.aes = FALSE, color = "#a6a6a6", size = .2, alpha = .8) +
geom_sf(data = maui_coast$osm_lines, inherit.aes = FALSE, color = "black", size = .8, alpha = .5) +
geom_sf(data = maui_np$osm_polygons, inherit.aes = FALSE, color = "brown", size = .2, alpha = .8) +
geom_sf(data = maui_protected$osm_polygons, inherit.aes = FALSE, color = "green", size = .2, alpha = .8) +
geom_point(data = inat_obs_df, aes(x = longitude, y = latitude, color = common_name, text = common_name)) + # here is our iNaturalist data
geom_sf_text(size = 1, data = maui_protected$osm_polygons, aes(label = name)) + #here we are adding some labels for our protected areas for context
coord_sf(xlim = c(-156.69726, -155.97909), ylim = c(20.57443, 21.03156), expand = TRUE) + # setting the limits of our map based on the lat/long we got from our OSM bounding box
ggtitle("Silverswords on Maui", subtitle = "Based on iNaturalist Data as of September 2024") +
theme_bw() +
labs(
color="Common Name", #this changes the title of our legend
x = "Longitude",
y = "Latitude"
)
## Warning in geom_point(data = inat_obs_df, aes(x = longitude, y = latitude, :
## Ignoring unknown aesthetics: text
p
## Warning in st_point_on_surface.sfc(sf::st_zm(x)): st_point_on_surface may not
## give correct results for longitude/latitude data
## Warning: Removed 4 rows containing missing values or values outside the scale range
## (`geom_text()`).
Great! We made a map of our data that gives the reader some context. One
thing to note: the Haleakalā National Park is missing from our map! If
we look at our
maui_np object, we can see that we didn’t
get any data in our osm_polygons dataset. It also doesn’t
show up in our protected_area dataset. This means that it
has not been added to OpenStreetMap yet.
This is a great example of one of the limitations of the OpenStreetMap dataset. Just because something isn’t in our dataset, doesn’t mean it isn’t there!
For our purposes, we can still move forward because we are just learning and exploring. If you were making this map for work or for school, you would want to find another source for the data, or add it to OpenStreetMap yourself.
What can we learn from this map? Are most of the silversword observations in protected areas or outside of them? Are they in rural areas or urban (hint: look at where most of the roads are)?
ggplotly(p,
tooltip = c("text"))
## Warning in st_point_on_surface.sfc(sf::st_zm(x)): st_point_on_surface may not
## give correct results for longitude/latitude data
We can use the leaflet package to visualize an
interactive map of the Silverswords on Maui. We can even customize what
shows up when we click on a data point to include things like the photos
associated with the records. The code chunk below customizes the data to
include in the pop-up:
inat_obs_popup_sf <- inat_obs_sf %>%
mutate(popup_html = paste0("<p><b>", common_name, "</b><br/>",
"<i>", scientific_name, "</i></p>",
"<p>Observed: ", datetime, "<br/>",
"User: ", user_login, "</p>",
"<p><img src='", image_url, "' style='width:100%;'/></p>")
)
The code chunk below creates a title for our map, and adds some formatting to our pop-ups.
htmltools::p("iNaturalist Observations of Silverswords on Maui Island",
htmltools::br(),
inat_obs_popup_sf$datetime %>%
as.Date() %>%
range(na.rm = TRUE) %>%
paste(collapse = " to "),
style = "font-weight:bold; font-size:110%;")
iNaturalist Observations of Silverswords on Maui Island
2001-07-17 to 2024-09-02
And here is where we create the map, using our
inat_obs_sf dataframe and the
inat_obs_popup_sf dataframe we created for our pop-up
labels.
leaflet(inat_obs_sf) %>%
setView(lng = -156.3, lat = 20.7, zoom = 12)%>%
addTiles() %>%
addCircleMarkers(data = inat_obs_popup_sf,
popup = ~popup_html,
radius = 5)
For your challenges this week, create the necessary code chunks below. You can copy/paste the code chunks from the tutorial and edit them as needed to complete the Challenges.
Challenge 1: Find the place_id that corresponds
to your target area and choose a genus or species you want to map and
find the taxon_id.
Challenge 2: Create a static map using the
ggplot function of your target area and target
genus/species.
Challenge 3: Create an interactive map using the
leaflet function of your target area and target
genus/species.
Explore the data available in iNaturalist for your chosen location. What data would be relevant to topics you want to focus on? Do you want to look at a particular species, maybe one that is endangered or invasive or culturally significant? Do you want to focus on the diversity of birds, or fungus? Try to create a draft map or two for your challenge location and topics.
The resources in this section are not required for this course! They are provided in case you want to learn more. Feel free to come back to them after you finish the course.
How to use iNaturalist’s Search URLs (Wiki), Part 1 and Part 2
Guide to Interactive web-based data visualization with R, plotly, and shiny